package com.jse;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpClient.Version;
import java.net.http.HttpRequest;
import java.net.http.HttpRequest.BodyPublisher;
import java.net.http.HttpRequest.BodyPublishers;
import java.net.http.HttpResponse;
import java.net.http.HttpResponse.BodyHandler;
import java.net.http.HttpResponse.BodyHandlers;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.Duration;
import java.util.Base64;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.function.Consumer;

import com.jse.json.Json;

@SuppressWarnings("unchecked")
public class Http {
	
	private final static Log log=Log.get("Http");
	public static final String CRLF = "\r\n";
	public static final String USER_AGENT="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 Edg/116.0.1938.76";

	public static Object send(String method, String url,Object body) {return send(null, method, url,body,0,null,false,null);}
	public static Object send(String method, String url,Object body,Map<String, String> headers) {return send(null, method, url, body, 0, headers,false,null);}
	public static Object send(String method, String url,Object body,Map<String, String> headers,Object type) {return send(null, method, url, body, 0, headers,false,type);}
	public static Object send(String method, String url,Object body,Map<String, String> headers,boolean async,Object type) {return send(null, method, url, body, 0, headers, async, type);}
	
	public static Object send(Version verion,String method, String url,Object body,int timeout,Map<String, String> headers,boolean async,Object type) {
		try {
			HttpRequest.Builder build=HttpRequest.newBuilder(URI.create(url));
			if(timeout!=0)build.timeout(Duration.ofMillis(timeout));
			if(verion==null)verion=Version.HTTP_1_1;
			build.version(verion);
			if(headers==null)headers=Map.of();
				headers.forEach((k,v)->{
					build.setHeader(k.toString(),v.toString());
				});
				if(!headers.containsKey("User-Agent")) {
					build.setHeader("User-Agent",USER_AGENT);
				}
			if(!headers.containsKey("referer"))build.setHeader("referer","http://jse.jdk17.cn");
			BodyPublisher bp=null;
			if(body==null) {
				bp=BodyPublishers.noBody();
			}else if(body instanceof File f) {
				bp=BodyPublishers.ofFile(f.toPath());
			}else if(body instanceof Path p) {
				bp=BodyPublishers.ofFile(p);
			}else if(body instanceof byte[] bs) {
				bp=BodyPublishers.ofByteArray(bs);
			}else if(body instanceof InputStream in) {
				bp=BodyPublishers.ofInputStream(()->in);
			}else if(body instanceof Collection<?> c){//body 为list则只支持json与xml
				var j=Json.jsonArray(c);
				String contentType=headers.get("Content-Type").toString();
				if(contentType.startsWith("application/xml")||contentType.startsWith("text/xml")){
					bp=BodyPublishers.ofString(j.toXml());
				}else {
					bp=BodyPublishers.ofString(j.toString());//普通文本处理
				}
			}else if(body instanceof Map<?,?> map){
					String content_Type=headers.get("Content-Type").toString();
					if(content_Type.startsWith("application/json")){
						bp=BodyPublishers.ofString(Json.toJson(map));
					}else if(content_Type.startsWith("application/xml")||content_Type.startsWith("text/xml")){
						bp=BodyPublishers.ofString(Json.jsonObject(map).toXml());
					}else if(content_Type.startsWith("multipart/form-data; boundary=")){
					String boundary=headers.get("Content-Type").toString().substring(30).trim();
					Map<String,String> textMap=new HashMap<>();//普通表单参数
					Map<String,File> fileMap=new HashMap<>();
					for(Object k:map.keySet()) {
						Object v=map.get(k);
							if(v ==null) {
								textMap.put(k.toString(),"");
							}else if(v instanceof File f) {
								fileMap.put(k.toString(),f);
							}else {
								textMap.put(k.toString(),v.toString());
							}
					}
					StringBuilder sb=new StringBuilder();
					ByteArrayOutputStream out=new ByteArrayOutputStream();
					textMap.forEach((k,v)->{//写普通表单参数
				        sb.append("--").append(boundary).append(CRLF)//写分隔符--${boundary}，并回车换行
				        //写描述信息：Content-Disposition: form-data; name="参数名"，并两个回车换行
				        .append(String.format("Content-Disposition: form-data; name=\"%s\"",k))
				        .append(CRLF).append(CRLF)
				        .append(v).append(CRLF);//写具体内容：参数值，并回车换行
					});
					out.write(sb.toString().getBytes());
					sb.setLength(0);
					var base64=headers.get("base64");
					fileMap.forEach((k,v)->{//写文件参数
						try (InputStream in = new FileInputStream(v)) {
							sb.append("--").append(boundary).append(CRLF);
				            String fileName = v.getName();
				            String contentDispositionStr = String.format("Content-Disposition: form-data; name=\"%s\"; filename=\"%s\"", k, fileName) + CRLF;
				            sb.append(contentDispositionStr);
				            String suffix=Fs.suffix(fileName);
				            String mimeType=Fs.mimeType(fileName,"application/octet-stream");
				            String contentType = "Content-Type: "+mimeType + CRLF + CRLF;
				            sb.append(contentType);
				            out.write(sb.toString().getBytes());
				            sb.setLength(0);
							if(base64!=null//jpg,png,jpeg,psd,gif,bmp,webp
								&&suffix.equals(base64)) {
								String img="data:"+mimeType+";base64,"
								+Awts.base64(in,suffix);
								out.write(img.getBytes());
							}else {
								out.write(in.readAllBytes());
							}
							out.write(CRLF.getBytes());//回车换行
				        } catch (Exception e) {
				        	e.printStackTrace();
				        }
					});
					out.write((CRLF+"--" + boundary + "--"+CRLF).getBytes());//回车换行
					bp=BodyPublishers.ofInputStream(()->{//转换formData
						return new ByteArrayInputStream(out.toByteArray());
					});
					}else {
						StringBuilder sb=new StringBuilder();//普通类型
						map.forEach((k,v)-> {
							sb.append("&").append(k).append("=").append(v);
						});
						bp=BodyPublishers.ofString(sb.substring(1));
					}
			}else {
				String s=body.toString();
				if(s.length()>0&&s.length()<255&&(s.charAt(0)!='{'||s.charAt(0)!='[')&&(s.charAt(0)=='/'||s.charAt(1)==':')&&Fs.exists(s)) {
					bp=BodyPublishers.ofFile(Path.of(s));
				}else {
					bp=BodyPublishers.ofString(body.toString());
				}
			}
			build.method(method,bp);
			BodyHandler<?> bh = BodyHandlers.ofString();
			if(type==null) {
				bh=BodyHandlers.ofString(StandardCharsets.UTF_8);
			}else if(type instanceof String s) {
				bh=BodyHandlers.ofByteArray();
				if(s.equals("byte")) {
					bh=BodyHandlers.ofByteArray();
				}else if(s.equals("in")){
					bh=BodyHandlers.ofInputStream();
				}else if(s.equals("stream")){
					bh=BodyHandlers.ofLines();
				}else if(s.equals("pub")){
					bh=BodyHandlers.ofPublisher();
				}else {
					bh=BodyHandlers.ofString(StandardCharsets.UTF_8);
				}
			}else if(type instanceof Path p) {
				if(Files.isDirectory(p)) {
					bh=BodyHandlers.ofFileDownload(p);
				}else {
					bh=BodyHandlers.ofFile(p);
				}
			}else if(type instanceof File f) {
				if(f.isDirectory()) {
					bh=BodyHandlers.ofFileDownload(f.toPath());
				}else {
					bh=BodyHandlers.ofFile(f.toPath());
				}
			}else if(type instanceof Charset charset){
				bh=BodyHandlers.ofString(charset);
			}else if(type instanceof Consumer c) {//Consumer<Optional<byte[]>> consumer
				bh=BodyHandlers.ofByteArrayConsumer(c);
			}
			if(async) {
				return HttpClient.newHttpClient().sendAsync(build.build(), bh).get();
			}else {
				return HttpClient.newHttpClient().send(build.build(), bh);
			}
		} catch (IOException | InterruptedException | ExecutionException e) {
			log.warn("%s %s err:%s",method,url,e.getMessage());
		}
		return null;
	}
	
	
	public static String get(String url) {return ((HttpResponse<String>)send("GET",url,null)).body();}
	public static String get(String url,Map<?, ?> param) {return ((HttpResponse<String>)send("GET",url,param)).body();}
	public static String post(String url,Map<?, ?> param){return ((HttpResponse<String>)send("POST",url,param)).body();}
	public static String postJson(String url,String json){return ((HttpResponse<String>)send("POST",url,json,Map.of("Content-Type","application/json"))).body();}
	public static String postJson(String url,Map<?, ?> json){return ((HttpResponse<String>)send("POST",url,json,Map.of("Content-Type","application/json"))).body();}
	public static String postXml(String url,String xml){return ((HttpResponse<String>)send("POST",url,xml,Map.of("Content-Type","application/xml"))).body();}
	public static String postXml(String url,Map<?, ?> xml){return ((HttpResponse<String>)send("POST",url,xml,Map.of("Content-Type","application/xml"))).body();}
	public static void down(String url,String path){down(url, path,true);}
	public static void down(String url,String path,boolean async){send(Version.HTTP_1_1,"GET",url,null,0,null,async,Path.of(path));}

	public static String basicAuth(String username,String password) {
		return "Basic " + Base64.getEncoder().encodeToString(username.concat(":").concat(password).getBytes(StandardCharsets.UTF_8));
	}
	
	public static void main(String[] args) throws InterruptedException {
//		down("https://cn.bing.com/th?id=OJ.ilrwlLiKV5AQIA&w=80&h=80&c=8&rs=1&pid=academic","d:/1.png",true);
//		Thread.sleep(2000l);
		System.out.println(basicAuth("zs", "123456"));
	}
}
